#!/usr/bin/python
#
# Copyright 2010 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# This code is not supported by Google
#

"""Simple program to convert a tab-separated .csv file to ContnetAPI 
  XML batch processing.  Once converted, you can run a batch upload of this file
  with the Content API for Shopping.
  
  >> The CommerceSearch usecase is set by default (ProductSearch is disabled).
  >> The ContentAPI can only process 1MB batch at a time so make segment the 
     input file to create a 1MB output file.
  
  custom attributes should be in the form c:attribute_name:attribute_type
  standard attributes maybe prefixed by g:
  
  http://code.google.com/apis/shopping/content/
  http://www.google.com/support/merchants/bin/answer.py?hl=en&answer=188494
  http://www.google.com/support/merchants/bin/answer.py?answer=160602&hl=en
  
usage: 
    python csvtocontentapi.py --in_file=<filename> --out_file=<filename>
    
"""
import fileinput
from xml.sax.saxutils import escape
import re
import getopt
import sys
import datetime
import xml.dom.minidom
import csv

class CSV2Content(object):
    
  def __init__(self, in_file, out_file):
    self.in_file_name = in_file
    self.out_file_name = out_file
       
    rownumber = 0
    BYTES_TO_KB = 0.0009765625
    batch_xml= ('<?xml version="1.0" encoding="UTF-8"?>'
             '<feed xmlns="http://www.w3.org/2005/Atom" '
             'xmlns:gd="http://schemas.google.com/g/2005" '
             'xmlns:sc="http://schemas.google.com/structuredcontent/2009" '
             'xmlns:scp="http://schemas.google.com/structuredcontent/2009/products" ' 
             'xmlns:app="http://www.w3.org/2007/app" '
             'xmlns:batch="http://schemas.google.com/gdata/batch"> ')
    self.log('Reading from ' + self.in_file_name)
    
    csreader = csv.reader(open(self.in_file_name, 'rb'), 
                            delimiter='\t', quotechar='|')         
    for row in csreader:
      entry = ''
      rownumber = rownumber + 1
      if rownumber == 1:
        attrib_type = row
      else:
        entry = ('<entry>\n'
                ' <batch:operation type="INSERT"/>\n'
                ' <app:control>\n'
                ' <sc:required_destination dest="CommerceSearch"/>\n'
                ' <sc:excluded_destination dest="ProductSearch"/>\n'
                ' </app:control>\n')
        attrib_entry = ''
        for i, v in enumerate(row):
          if (sys.getsizeof(batch_xml)*BYTES_TO_KB) > 1000:
            self.log('Batch XML must be smaller than 1MB ')
            exit()
          if v != '':
            if attrib_type[i].startswith('c:'):
              attrib_params = attrib_type[i].split(':')
              aname = attrib_params[1]
              atype = ''
              if len(attrib_params) == 3:
                atype = attrib_params[2]
                if atype == 'string':
                  atype = 'text'
              a = v.split(",")
              for s in a:
                val=  s.replace('"','').strip()
                attrib_entry = attrib_entry + ('<sc:attribute name="' + aname + 
                                               '" type="' + atype + '">' + 
                                               escape(val) + '</sc:attribute>\n')
            else:
              if attrib_type[i].startswith('g:'):
                attrib_params = attrib_type[i].split(':')
                aname = attrib_params[1]
              else:
                aname = attrib_type[i]
              if aname == 'id':
                attrib_entry = (attrib_entry + '<sc:id>' + v + '</sc:id>\n' +
                               '<batch:id>' + v + '</batch:id>\n')              
              elif aname == 'title':
                v =  self.remove_control_chars(v)  
                attrib_entry = attrib_entry + '<title>' + escape(v) + '</title>\n'
              elif aname == 'link':
                attrib_entry = attrib_entry + '<link rel="alternate" type="text/html" href="' + escape(v) + '"/>\n'                    
              elif aname == 'description':
                v =  self.remove_control_chars(v)  
                attrib_entry = attrib_entry + '<content type="text">' + escape(v) + '</content>\n'
              elif aname == 'price':
                attrib_entry = attrib_entry + '<sc:price unit="usd">' + v + '</sc:price>\n'                                                             
              elif aname == 'content_language':
                attrib_entry = attrib_entry + '<sc:content_language>' + v + '</sc:content_language>\n' 
              elif aname == 'target_country':
                attrib_entry = attrib_entry + '<sc:target_country>' + v + '</sc:target_country>\n'                                  
              elif aname == 'expiration_date':
                attrib_entry = attrib_entry + '<sc:expiration_date>' + v + '</sc:expiration_date>\n'
              elif aname == 'image_link':
                attrib_entry = attrib_entry + '<sc:image_link>' + escape(v) + '</sc:image_link>\n'
              else:
                a = v.split(",")
                for s in a:
                  val=  s.replace('"','').strip()
                  aname = aname.replace(" ","_")
                  if (aname != 'excluded_destination'):
                    attrib_entry = attrib_entry + ('<scp:' + aname + '>' +
                                                   escape(self.remove_control_chars(val))
                                                   + '</scp:' + aname + '>\n')                                                          
        if attrib_entry != '':
          entry = entry + attrib_entry  + '</entry>\n'
      batch_xml = batch_xml +  entry
      
    batch_xml = batch_xml + '</feed>'
    self.log('Processed ' + str(rownumber-1) + ' items ['
           + str(sys.getsizeof(batch_xml)*BYTES_TO_KB) + ' KB]' )    
    try:
      self.log('Attempting to parse batch XML file.')
      xmldoc = xml.dom.minidom.parseString(batch_xml)
      self.log('XML batch file well formed.')
    except Exception, e:
      self.log('Error: ' + str(e))
      self.log('XML batch file not well formed...continue anyway' )
   
    try:
      out_file = open(self.out_file_name, 'w')
      out_file.write(batch_xml)
      out_file.close()
      self.log('Writing to ' + self.out_file_name )
    except IOError:
      self.log('Error Saving xml File')
   
  def remove_control_chars(self,s):
    control_chars = ''.join(map(unichr, range(0,32) + range(127,160)))
    control_char_re = re.compile('[%s]' % re.escape(control_chars))  
    return control_char_re.sub('', s)
  
  def log(self,msg):
    print ('[%s] %s') % (datetime.datetime.now(), msg)  
  
if __name__ == '__main__':
  in_file = None
  out_file = None
  delimiter = 'tab'
  try:
    opts, args = getopt.getopt(sys.argv[1:], None, ["in_file=","out_file="])
  except getopt.GetoptError:
    print 'Please specify --delimiter= --in_file=  and --out_file='
    sys.exit(1)

  for opt, arg in opts:
    if opt == "--in_file":
      in_file = arg
    if opt == "--out_file":
      out_file = arg

  CSV2Content(in_file, out_file)
